/* -*-  Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*-
 *
 * Copyright (c) 1996 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *  This product includes software developed by the Computer Systems
 *  Engineering Group at Lawrence Berkeley Laboratory and the Daedalus
 *  research group at UC Berkeley.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Header: /cvsroot/nsnam/ns-2/mac/wireless-phy.cc,v 1.28 2007/09/04 04:32:18 tom_henderson Exp $
 *
 * Ported from CMU/Monarch's code, nov'98 -Padma Haldar.
 * wireless-phy.cc
 */

#include <math.h>

#include <packet.h>

#include <mobilenode.h>
#include <phy.h>
#include <propagation.h>
#include <modulation.h>
#include <omni-antenna.h>
#include "wireless-phy-DVCS.h"
#include <packet.h>
#include <ip.h>
#include <agent.h>
#include <trace.h>
#include <sys/param.h>  /* for MIN/MAX */

#include "diffusion/diff_header.h"

void Sleep_TimerDVCS::expire(Event *) {
    a_->UpdateSleepEnergy();
}

/* ======================================================================
 WirelessPhy Interface
 ====================================================================== */
static class WirelessPhyDVCSClass: public TclClass {
public:
    WirelessPhyDVCSClass() :
        TclClass("Phy/WirelessPhy/DVCS") {
    }
    TclObject* create(int, const char* const *) {
        return (new WirelessPhyDVCS);
    }
} class_WirelessPhyDVCS;

WirelessPhyDVCS::WirelessPhyDVCS() :
    Phy(), sleep_timer_(this), status_(IDLE), boresight_(0) {
    /*
     *  It sounds like 10db should be the capture threshold.
     *
     *  If a node is presently receiving a packet a a power level
     *  Pa, and a packet at power level Pb arrives, the following
     *  comparion must be made to determine whether or not capture
     *  occurs:
     *
     *    10 * log(Pa) - 10 * log(Pb) > 10db
     *
     *  OR equivalently
     *
     *    Pa/Pb > 10.
     *
     */
    bind("CPThresh_", &CPThresh_);
    bind("CSThresh_", &CSThresh_);
    bind("RXThresh_", &RXThresh_);
    //bind("bandwidth_", &bandwidth_);
    bind("Pt_", &Pt_);
    bind("freq_", &freq_);
    bind("L_", &L_);

    lambda_ = SPEED_OF_LIGHT / freq_;

    node_ = 0;
    ant_ = 0;
    propagation_ = 0;
    modulation_ = 0;

    // Assume AT&T's Wavelan PCMCIA card -- Chalermek
    //  Pt_ = 8.5872e-4; // For 40m transmission range.
    //      Pt_ = 7.214e-3;  // For 100m transmission range.
    //      Pt_ = 0.2818; // For 250m transmission range.
    //  Pt_ = pow(10, 2.45) * 1e-3;         // 24.5 dbm, ~ 281.8mw

    Pt_consume_ = 0.660; // 1.6 W drained power for transmission
    Pr_consume_ = 0.395; // 1.2 W drained power for reception

    //  P_idle_ = 0.035; // 1.15 W drained power for idle

    P_idle_ = 0.0;
    P_sleep_ = 0.00;
    T_sleep_ = 10000;
    P_transition_ = 0.00;
    T_transition_ = 0.00; // 2.31 change: Was not initialized earlier
    node_on_ = 1;

    channel_idle_time_ = NOW;
    update_energy_time_ = NOW;
    last_send_time_ = NOW;

    sleep_timer_.resched(1.0);
}

int WirelessPhyDVCS::command(int argc, const char* const * argv) {
    TclObject *obj;

    if (argc == 2) {
        if (strcasecmp(argv[1], "NodeOn") == 0) {
            node_on();

            if (em() == NULL)
                return TCL_OK;
            if (NOW > update_energy_time_) {
                update_energy_time_ = NOW;
            }
            return TCL_OK;
        } else if (strcasecmp(argv[1], "NodeOff") == 0) {
            node_off();

            if (em() == NULL)
                return TCL_OK;
            if (NOW > update_energy_time_) {
                em()->DecrIdleEnergy(NOW - update_energy_time_, P_idle_);
                update_energy_time_ = NOW;
            }
            return TCL_OK;
        }
    } else if (argc == 3) {
        if (strcasecmp(argv[1], "setTxPower") == 0) {
            Pt_consume_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setRxPower") == 0) {
            Pr_consume_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setIdlePower") == 0) {
            P_idle_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setSleepPower") == 0) {
            P_sleep_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setSleepTime") == 0) {
            T_sleep_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setTransitionPower") == 0) {
            P_transition_ = atof(argv[2]);
            return TCL_OK;
        } else if (strcasecmp(argv[1], "setTransitionTime") == 0) {
            T_transition_ = atof(argv[2]);
            return TCL_OK;
        } else if ((obj = TclObject::lookup(argv[2])) == 0) {
            fprintf(stderr, "WirelessPhy: %s lookup of %s failed\n", argv[1],
                    argv[2]);
            return TCL_ERROR;
        } else if (strcmp(argv[1], "propagation") == 0) {
            assert(propagation_ == 0);
            propagation_ = (Propagation*) obj;
            return TCL_OK;
        } else if (strcasecmp(argv[1], "antenna") == 0) {
            ant_ = (Antenna*) obj;
            return TCL_OK;
        } else if (strcasecmp(argv[1], "node") == 0) {
            assert(node_ == 0);
            node_ = (Node *) obj;
            return TCL_OK;
        }
    }
    return Phy::command(argc, argv);
}

void WirelessPhyDVCS::sendDown(Packet *p) {
    /*
     * Sanity Check
     */
    assert(initialized());

    if (em()) {
        //node is off here...
        if (Is_node_on() != true) {
            Packet::free(p);
            return;
        }
        if (Is_node_on() == true && Is_sleeping() == true) {
            em()-> DecrSleepEnergy(NOW - update_energy_time_, P_sleep_);
            update_energy_time_ = NOW;

        }

    }
    /*
     * Decrease node's energy
     */
    if (em()) {
        if (em()->energy() > 0) {

            double txtime = hdr_cmn::access(p)->txtime();
            double start_time = MAX(channel_idle_time_, NOW);
            double end_time = MAX(channel_idle_time_, NOW+txtime);
            double actual_txtime = end_time - start_time;

            if (start_time > update_energy_time_) {
                em()->DecrIdleEnergy(start_time - update_energy_time_, P_idle_);
                update_energy_time_ = start_time;
            }

            /* It turns out that MAC sends packet even though, it's
             receiving some packets.

             if (txtime-actual_txtime > 0.000001) {
             fprintf(stderr,"Something may be wrong at MAC\n");
             fprintf(stderr,"act_tx = %lf, tx = %lf\n", actual_txtime, txtime);
             }
             */

            // Sanity check
            double temp = MAX(NOW,last_send_time_);

            /*
             if (NOW < last_send_time_) {
             fprintf(stderr,"Argggg !! Overlapping transmission. NOW %lf last %lf temp %lf\n", NOW, last_send_time_, temp);
             }
             */

            double begin_adjust_time = MIN(channel_idle_time_, temp);
            double finish_adjust_time = MIN(channel_idle_time_, NOW+txtime);
            double gap_adjust_time = finish_adjust_time - begin_adjust_time;
            if (gap_adjust_time < 0.0) {
                fprintf(stderr, "What the heck ! negative gap time.\n");
            }

            if ((gap_adjust_time > 0.0) && (status_ == RECV)) {
                em()->DecrTxEnergy(gap_adjust_time, Pt_consume_ - Pr_consume_);
            }

            em()->DecrTxEnergy(actual_txtime, Pt_consume_);
            //         if (end_time > channel_idle_time_) {
            //             status_ = SEND;
            //         }
            //
            status_ = IDLE;

            last_send_time_ = NOW + txtime;
            channel_idle_time_ = end_time;
            update_energy_time_ = end_time;

            if (em()->energy() <= 0) {
                em()->setenergy(0);
                ((MobileNode*) node())->log_energy(0);
            }

        } else {

            // log node energy
            if (em()->energy() > 0) {
                ((MobileNode *) node_)->log_energy(1);
            }
            //
            Packet::free(p);
            return;
        }
    }

    /*
     *  Stamp the packet with the interface arguments
     */
    p->txinfo_.stamp((MobileNode*) node(), ant_->copy(), Pt_, lambda_);

    // Send the packet
    channel_->recv(p, this);
}

int WirelessPhyDVCS::sendUp(Packet *p) {
    /*
     * Sanity Check
     */
    assert(initialized());

    PacketStamp s;
    double Pr;
    int pkt_recvd = 0;

    Pr = p->txinfo_.getTxPr();

    // if the node is in sleeping mode, drop the packet simply
    if (em()) {
        if (Is_node_on() != true) {
            pkt_recvd = 0;
            goto DONE;
        }

        if (Is_sleeping() == true && (Is_node_on() == true)) {
            pkt_recvd = 0;
            goto DONE;
        }

    }
    // if the energy goes to ZERO, drop the packet simply
    if (em()) {
        if (em()->energy() <= 0) {
            pkt_recvd = 0;
            goto DONE;
        }
    }

    if (propagation_) {
        s.stamp((MobileNode*) node(), ant_, 0, lambda_);
        Pr = propagation_->Pr(&p->txinfo_, &s, this);
        if (Pr < CSThresh_) {
            pkt_recvd = 0;
            goto DONE;
        }
        if (Pr < RXThresh_) {
            /*
             * We can detect, but not successfully receive
             * this packet.
             */
            hdr_cmn *hdr = HDR_CMN(p);
            hdr->error() = 1;
#if DEBUG > 3
            printf("SM %f.9 _%d_ drop pkt from %d low POWER %e/%e\n",
                    Scheduler::instance().clock(), node()->index(),
                    p->txinfo_.getNode()->index(),
                    Pr,RXThresh);
#endif
        }
    }
    if (modulation_) {
        hdr_cmn *hdr = HDR_CMN(p);
        hdr->error() = modulation_->BitError(Pr);
    }

    /*
     * The MAC layer must be notified of the packet reception
     * now - ie; when the first bit has been detected - so that
     * it can properly do Collision Avoidance / Detection.
     */
    pkt_recvd = 1;

    DONE: p->txinfo_.getAntenna()->release();

    /* WILD HACK: The following two variables are a wild hack.
     They will go away in the next release...
     They're used by the mac-802_11 object to determine
     capture.  This will be moved into the net-if family of
     objects in the future. */
    p->txinfo_.RxPr = Pr;
    p->txinfo_.CPThresh = CPThresh_;

    /*
     * Decrease energy if packet successfully received
     */
    if (pkt_recvd && em()) {

        double rcvtime = hdr_cmn::access(p)->txtime();
        // no way to reach here if the energy level < 0

        double start_time = MAX(channel_idle_time_, NOW);
        double end_time = MAX(channel_idle_time_, NOW+rcvtime);
        double actual_rcvtime = end_time - start_time;

        if (start_time > update_energy_time_) {
            em()->DecrIdleEnergy(start_time - update_energy_time_, P_idle_);
            update_energy_time_ = start_time;
        }

        em()->DecrRcvEnergy(actual_rcvtime, Pr_consume_);
        /*
         if (end_time > channel_idle_time_) {
         status_ = RECV;
         }
         */
        channel_idle_time_ = end_time;
        update_energy_time_ = end_time;

        status_ = IDLE;

        /*
         hdr_diff *dfh = HDR_DIFF(p);
         printf("Node %d receives (%d, %d, %d) energy %lf.\n",
         node()->address(), dfh->sender_id.addr_,
         dfh->sender_id.port_, dfh->pk_num, node()->energy());
         */

        // log node energy
        if (em()->energy() > 0) {
            ((MobileNode *) node_)->log_energy(1);
        }

        if (em()->energy() <= 0) {
            // saying node died
            em()->setenergy(0);
            ((MobileNode*) node())->log_energy(0);
        }
    }

    return pkt_recvd;
}

void WirelessPhyDVCS::node_on() {

    node_on_ = TRUE;
    status_ = IDLE;

    if (em() == NULL)
        return;
    if (NOW > update_energy_time_) {
        update_energy_time_ = NOW;
    }
}

void WirelessPhyDVCS::node_off() {

    node_on_ = FALSE;
    status_ = SLEEP;

    if (em() == NULL)
        return;
    if (NOW > update_energy_time_) {
        em()->DecrIdleEnergy(NOW - update_energy_time_, P_idle_);
        update_energy_time_ = NOW;
    }
}

void WirelessPhyDVCS::node_wakeup() {

    if (status_ == IDLE)
        return;

    if (em() == NULL)
        return;

    if (NOW > update_energy_time_ && (status_ == SLEEP)) {
        //the power consumption when radio goes from SLEEP mode to IDLE mode
        em()->DecrTransitionEnergy(T_transition_, P_transition_);

        em()->DecrSleepEnergy(NOW - update_energy_time_, P_sleep_);
        status_ = IDLE;
        update_energy_time_ = NOW;

        // log node energy
        if (em()->energy() > 0) {
            ((MobileNode *) node_)->log_energy(1);
        } else {
            ((MobileNode *) node_)->log_energy(0);
        }
    }
}

void WirelessPhyDVCS::node_sleep() {
    //
    //        node_on_= FALSE;
    //
    if (status_ == SLEEP)
        return;

    if (em() == NULL)
        return;

    if (NOW > update_energy_time_ && (status_ == IDLE)) {
        //the power consumption when radio goes from IDLE mode to SLEEP mode
        em()->DecrTransitionEnergy(T_transition_, P_transition_);

        em()->DecrIdleEnergy(NOW - update_energy_time_, P_idle_);
        status_ = SLEEP;
        update_energy_time_ = NOW;

        // log node energy
        if (em()->energy() > 0) {
            ((MobileNode *) node_)->log_energy(1);
        } else {
            ((MobileNode *) node_)->log_energy(0);
        }
    }
}
//
void WirelessPhyDVCS::dump(void) const {
    Phy::dump();
    fprintf(stdout, "\tPt: %f, Gt: %f, Gr: %f, lambda: %f, L: %f\n", Pt_,
            ant_->getTxGain(0, 0, 0, lambda_),
            ant_->getRxGain(0, 0, 0, lambda_), lambda_, L_);
    //fprintf(stdout, "\tbandwidth: %f\n", bandwidth_);
    fprintf(stdout, "--------------------------------------------------\n");
}

void WirelessPhyDVCS::UpdateIdleEnergy() {
    if (em() == NULL) {
        return;
    }
    if (NOW > update_energy_time_ && (Is_node_on() == TRUE && status_ == IDLE)) {
        em()-> DecrIdleEnergy(NOW - update_energy_time_, P_idle_);
        update_energy_time_ = NOW;
    }

    // log node energy
    if (em()->energy() > 0) {
        ((MobileNode *) node_)->log_energy(1);
    } else {
        ((MobileNode *) node_)->log_energy(0);
    }

    //  idle_timer_.resched(10.0);
}

double WirelessPhyDVCS::getDist(double Pr, double Pt, double Gt, double Gr,
        double hr, double ht, double L, double lambda) {
    if (propagation_) {
        return propagation_->getDist(Pr, Pt, Gt, Gr, hr, ht, L, lambda);
    }
    return 0;
}

//
void WirelessPhyDVCS::UpdateSleepEnergy() {
    if (em() == NULL) {
        return;
    }
    if (NOW > update_energy_time_ && (Is_node_on() == TRUE && Is_sleeping()
            == true)) {
        em()-> DecrSleepEnergy(NOW - update_energy_time_, P_sleep_);
        update_energy_time_ = NOW;
        // log node energy
        if (em()->energy() > 0) {
            ((MobileNode *) node_)->log_energy(1);
        } else {
            ((MobileNode *) node_)->log_energy(0);
        }
    }

    //A hack to make states consistent with those of in Energy Model for AF
    int static s = em()->sleep();
    if (em()->sleep() != s) {

        s = em()->sleep();
        if (s == 1)
            node_sleep();
        else
            node_wakeup();
        //      printf("\n AF hack %d\n",em()->sleep());
    }

    sleep_timer_.resched(10.0);
}
